<?php
/**
 * TinyCatPHP 极简、快速、创新、易用、高性能的PHP应用开发框架
 * User: CnTaiGe <hst042@163.com>
 * Date: 2017/11/15
 */

namespace TinyCatPHP\libs\db\drive;

use MongoDB\Driver\BulkWrite;
use MongoDB\Driver\Command;
use MongoDB\Driver\Manager;
use MongoDB\Driver\Query;
use TinyCatPHP\exception\SysException;
use TinyCatPHP\libs\db\DBInterface;

class Mongodb implements DBInterface
{
    protected $_manager;
    protected $_db;
    protected $_collection;
    protected $_sql;
    protected $_prefix;

    public function __construct($config)
    {
        $this->_db = $config['db_name'];
        $uri = 'mongodb://';
        if (!empty($config['user'])) $uri .= $config['user'] . ':' . $config['pwd'] . '@';
        $uri .= $config['host'] . (empty($config['port']) ? '' : ':' . $config['port']) . '/' . $config['db_name'];
        $this->_manager = new Manager($uri);
        $this->_prefix = $config['prefix'];
    }


    /**
     * 设置表
     * @param $table
     * @return DBInterface
     */
    public function setTable($table)
    {
        $this->_collection = $this->_prefix . $table;
        return $this;
    }

    /**
     * 插入数据
     * @param string $table
     * @param array $data
     * @return mixed
     */
    public function insert($data, $table = '')
    {
        $_cols = empty($table) ? $this->_db . '.' . $this->_collection : $this->_db . '.' . $table;
        $wt = new BulkWrite();
        $_id = $wt->insert($data);
        $re = $this->_manager->executeBulkWrite($_cols, $wt);
        $c = $re->getInsertedCount();
        $this->_sql = "{$_cols}.insert(" . json_encode($data, JSON_UNESCAPED_UNICODE) . ")";
        if ($c === 1 && $_id !== null) {
            return $_id . '';
        }
        return $c;
    }

    /**
     * 批量插入数据
     * @param $datas
     * @param string $table
     * @return array|int id
     */
    public function insertMany($datas, $table = '')
    {
        $_cols = empty($table) ? $this->_db . '.' . $this->_collection : $this->_db . '.' . $table;
        $_ids = [];
        $wt = new BulkWrite();
        foreach ($datas as $data) {
            if (is_array($data))
                $_ids[] = ($wt->insert($data)) . '';
        }
        $re = $this->_manager->executeBulkWrite($_cols, $wt);
        $c = $re->getInsertedCount();
        $this->_sql = "{$_cols}.insertMany(" . json_encode($datas, JSON_UNESCAPED_UNICODE) . ")";
        if (!empty($_ids) && count($_ids) === $c) {
            return $_ids;
        }
        return $c;
    }

    /**
     * 查询数据
     * @param array $filter 查询条件
     * @param array $options 查询附加条件 ['sort'=>['fi'=>-1]]
     * @param $table
     * @return array 返回结果集
     */
    public function query($filter = [], $options = [], $table = '')
    {
        $_cols = empty($table) ? $this->_db . '.' . $this->_collection : $this->_db . '.' . $table;
        $query = new Query($filter, $options);
        $cursor = $this->_manager->executeQuery($_cols, $query);
        $data = [];
        $this->_sql = $_cols . '.find(' . json_encode($filter, JSON_UNESCAPED_UNICODE) . ')' . (empty($options) ? '' : '.' . (implode('.', array_keys($options))) . '...');
        foreach ($cursor as $doc) {
            $doc->_id = $doc->_id . '';
            $data[] = (array)$doc;
        }
        return $data;
    }

    /**
     * 执行sql语句 删除、添加、更新
     * @param $sql
     * @return mixed
     * @throws
     */
    public function execute($sql)
    {
        throw new SysException("mongodb无需调用此方法");
    }

    /**
     * 更新数据
     * @param $where array 更新数组
     * @param $data
     * @param string $table
     * @param $updateOptions
     * @return mixed
     */
    public function update($data, $where = [], $updateOptions = ['multi' => true, 'upsert' => false], $table = '')
    {
        $_cols = empty($table) ? $this->_db . '.' . $this->_collection : $this->_db . '.' . $table;
        $wt = new  BulkWrite();
        $wt->update($where, ['$set' => $data], $updateOptions);
        $re = $this->_manager->executeBulkWrite($_cols, $wt);
        $updateOptionsStr = '';
        foreach ($updateOptions as $kk => $vv) {
            $updateOptionsStr .= $kk . ':' . var_export($vv, true) . ',';
        }
        $this->_sql = $_cols . '.update(' . json_encode($where, JSON_UNESCAPED_UNICODE) . ','
            . json_encode(['$set' => $data], JSON_UNESCAPED_UNICODE) . ','
            . '{' . rtrim($updateOptionsStr, ',') . '}' . ')';
        return $re->getModifiedCount();
    }

    /**
     * 删除数据
     * @param $where array 删除条件
     * @param string $table
     * @param array $values
     * @return mixed
     */
    public function delete($where, array $values = [], $table = '')
    {
        $_cols = empty($table) ? $this->_db . '.' . $this->_collection : $this->_db . '.' . $table;
        if (empty($where)) {
            //需指定条件
            return 0;
        }
        $wt = new  BulkWrite();
        $wt->delete($where);
        $this->_sql = $_cols . '.remove(' . json_encode($where, JSON_UNESCAPED_UNICODE) . ')';
        $re = $this->_manager->executeBulkWrite($_cols, $wt);
        return $re->getDeletedCount();
    }

    /**
     * 启动事务
     * @return bool
     * @throws
     */
    public function begintrans()
    {
        throw new SysException("mongodb无事务");
    }

    /**
     * 提交事务
     * @return bool
     */
    public function commit()
    {

    }

    /**
     * 回滚事务
     * @return bool
     */
    public function rollback()
    {

    }

    /**
     * 获取最后查询的sql
     * @return mixed
     */
    public function getSql()
    {
        return $this->_sql;
    }

    /**
     * 执行查询命令
     * @param array $com
     * @return array
     */
    public function command($com = [])
    {
        $c = new Command($com);
        $cursor = $this->_manager->executeCommand($this->_db, $c);
        return $cursor->toArray();
    }

    /**
     * 聚合查询
     * @param $pipeline
     * @return array
     */
    public function aggregate($pipeline)
    {
        $res = $this->command([
            'aggregate' => $this->_collection,
            'pipeline' =>
                $pipeline
        ]);
        return (array)($res[0]->result[0]);
    }

    public function __call($name, $arguments)
    {
        $names = explode('_', $name);
        if (count($names) > 0 && $names[0] === 'find') {
            //处理快捷查询
            unset($names[0]);
            $sql = [];
            $field = implode('_', $names);
            $orAnd = function ($oA) use ($field, $sql, $arguments) {
                if (strpos($field, $oA) !== false) {
                    $ors = explode($oA, $field);
                    if (count($ors) === 2) {
                        if (count($arguments) !== 2)
                            throw new SysException('查询参数个数不正确');
                        if ($oA === '_OR_' || $oA === '_or_')
                            $sql = ['$or' => [
                                [$ors[0] => $arguments[0]],
                                [$ors[1] => $arguments[1]]
                            ]];
                        else
                            $sql = [
                                $ors[0] => $arguments[0],
                                $ors[1] => $arguments[1]
                            ];
                    }
                }
                return $sql;
            };
            if (count($names) === 1) {
                $sql = [$field => $arguments[0]];
            } else {
                if (in_array('OR', $names))
                    $sql = $orAnd('_OR_');
                if (in_array('or', $names))
                    $sql = $orAnd('_or_');
                if (in_array('and', $names))
                    $sql = $orAnd('_and_');
                if (in_array('AND', $names))
                    $sql = $orAnd('_AND_');
            }
            $res = $this->query($sql, ['limit' => 1]);
            return isset($res[0]) ? $res[0] : [];
        }
        throw new  SysException('查不到对应的方法');
    }

    /**
     * @param string|array $where 查条件参数
     * @param $table
     * @return int 返回的数量
     */
    public function count($where = '', $table = '')
    {
        $_cols = empty($table) ? $this->_collection : $table;
        $re = $this->command([
                'count' => $_cols,
                'query' => $where
            ]
        );
        $this->_sql = $this->_db . '.' . $_cols . '.find(' . json_encode($where, JSON_UNESCAPED_UNICODE) . ').count()';
        return $re[0]->n;
    }
}